package com.runssnail.demo.spring.config;

import com.eyeieye.melos.util.StringUtil;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PlaceholderConfigurerSupport;
import org.springframework.core.SpringProperties;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringValueResolver;

import java.util.Collections;
import java.util.Map;
import java.util.Properties;

/**
 * Created by zhengwei on 2016/3/3.
 */
public class ZookeeperConfigManager extends PlaceholderConfigurerSupport implements ConfigManager, InitializingBean, IZkDataListener {

    /**
     * Never check system properties.
     */
    public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;

    /**
     * Check system properties if not resolvable in the specified properties.
     * This is the default.
     */
    public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;

    /**
     * Check system properties first, before trying the specified properties.
     * This allows system properties to override any other property source.
     */
    public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;

    private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;

    private boolean searchSystemEnvironment =
            !SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME);

    /**
     * 配置路径, eg:/app/config/benny
     */
    private String configPath;

    /**
     * 前缀
     */
    private String prefixPath = "/app/config/";

    /**
     * 应用名称
     */
    private String appName;

    /**
     * zk client
     */
    private ZkClient zkClient;

    /**
     * 从zk动态取的配置数据
     */
    private Map<String, String> configs;

    public ZkClient getZkClient() {
        return zkClient;
    }

    public void setZkClient(ZkClient zkClient) {
        this.zkClient = zkClient;
    }

    public String getPrefixPath() {
        return prefixPath;
    }

    public void setPrefixPath(String prefixPath) {
        this.prefixPath = prefixPath;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {

        Properties merged = new Properties(props);

        if (this.configs != null && !this.configs.isEmpty()) {
            merged.putAll(this.configs);
        }

        StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(merged);
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        if (StringUtil.isBlank(this.appName)) {
            throw new IllegalArgumentException("the appName is required");
        }

        if (StringUtil.isBlank(this.prefixPath)) {
            throw new IllegalArgumentException("the prefixPath is required");
        }

        this.configPath = buildConfigPath();

        if (!zkClient.exists(this.configPath)) {
            zkClient.createPersistent(this.configPath, true);
        }

        // 订阅消息
        zkClient.subscribeDataChanges(this.configPath, this);

        // read from zk
        this.configs = (Map<String, String>) zkClient.readData(this.configPath);

        if (this.configs == null || this.configs.isEmpty()) {
            if (logger.isWarnEnabled()) {
                logger.warn("can not get configs from zk");
            }
        } else {
            if (logger.isInfoEnabled()) {
                logger.info("get configs from zk success, configs=" + this.configs);
            }
        }
    }

    private String buildConfigPath() {
        if (!this.prefixPath.endsWith("/")) {
            this.prefixPath = this.prefixPath + "/";
        }
        return this.prefixPath + this.appName;
    }

    protected String resolveSystemProperty(String key) {
        try {
            String value = System.getProperty(key);
            if (value == null && this.searchSystemEnvironment) {
                value = System.getenv(key);
            }
            return value;
        } catch (Throwable ex) {
            logger.error("Could not access system property '" + key + "': " + ex);
            return null;
        }
    }

    protected String resolvePlaceholder(String placeholder, Properties props) {
        return props.getProperty(placeholder);
    }

    protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
        String propVal = null;
        if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
            propVal = resolveSystemProperty(placeholder);
        }
        if (propVal == null) {
            propVal = resolvePlaceholder(placeholder, props);
        }
        if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
            propVal = resolveSystemProperty(placeholder);
        }
        return propVal;
    }

    @Override
    public void handleDataChange(String path, Object data) throws Exception {
        if (logger.isInfoEnabled()) {
            logger.info("configs have changed, new configs=" + data);
        }
        this.configs = (Map<String, String>) data;
    }

    @Override
    public void handleDataDeleted(String s) throws Exception {
        if (logger.isInfoEnabled()) {
            logger.info("configs have deleted");
        }
        this.configs = Collections.emptyMap();
    }

    @Override
    public String get(String key) {
        return this.configs != null ? this.configs.get(key) : null;
    }

    private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

        private final PropertyPlaceholderHelper helper;

        private final PropertyPlaceholderHelper.PlaceholderResolver resolver;

        public PlaceholderResolvingStringValueResolver(Properties props) {
            this.helper = new PropertyPlaceholderHelper(
                    placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
            this.resolver = new PropertyPlaceholderConfigurerResolver(props);
        }

        @Override
        public String resolveStringValue(String strVal) throws BeansException {
            String value = this.helper.replacePlaceholders(strVal, this.resolver);
            return (value.equals(nullValue) ? null : value);
        }
    }


    private class PropertyPlaceholderConfigurerResolver implements PropertyPlaceholderHelper.PlaceholderResolver {

        private final Properties props;

        private PropertyPlaceholderConfigurerResolver(Properties props) {
            this.props = props;
        }

        @Override
        public String resolvePlaceholder(String placeholderName) {
            return ZookeeperConfigManager.this.resolvePlaceholder(placeholderName, props, systemPropertiesMode);
        }
    }
}
